#include <cereal/archives/binary.hpp>
#include <cereal/archives/json.hpp>
#include <cereal/types/memory.hpp>
#include <cereal/types/unordered_map.hpp>
#include <class_example.hpp>
#include <fstream>
#include <gtest/gtest.h>
#include <random>

std::random_device rd;
std::mt19937 eng(rd());
std::vector<std::string> strs{"1",  "2",  "3",  "4",  "5",  "6",  "7",  "8",
                              "11", "12", "13", "14", "15", "16", "17", "18"};
std::uniform_int_distribution<> distr(0, strs.size() - 1);

std::string random_str() { return strs[distr(eng)]; }

Address random_address() {
  return Address{
      .street{random_str()},
      .city{random_str()},
      .state{random_str()},
  };
}

Employee::PersonalInfo random_personal_info() {
  return Employee::PersonalInfo{
      .name{random_str()},
      .address{random_address()},
      .phone{random_str()},
  };
}

std::map<int64_t, Employee::PerformanceRating> random_ratings() {
  std::map<int64_t, Employee::PerformanceRating> result;
  for (int64_t year = 2010; year < 2020; ++year) {
    auto val = distr(eng);
    auto rating = Employee::PerformanceRating::Exceed;
    if ((val % 3) == 1) {
      rating = Employee::PerformanceRating::PIP;
    }
    if ((val % 3) == 2) {
      rating = Employee::PerformanceRating::Meet_Expectation;
    }
    result[year] = rating;
  }
  return result;
}

TEST(TestClass, Getter) {
  const auto personal_info = random_personal_info();
  std::string department{"IT"};
  const auto ratings = random_ratings();
  Employee employee(personal_info, department, ratings);

  EXPECT_EQ(employee.get_department(), department);
  EXPECT_EQ(employee.get_person_info().name, personal_info.name);
  EXPECT_EQ(employee.get_person_info().address.city,
            personal_info.address.city);
  EXPECT_EQ(employee.get_year_to_perf().at(2015), ratings.at(2015));

  employee.get_person_info_mutable().address.street = "LCC";
  EXPECT_EQ(employee.get_person_info().address.street, "LCC");
  employee.get_year_to_perf_mutable()[0] = Employee::PerformanceRating::PIP;
  EXPECT_EQ(employee.get_year_to_perf().at(0),
            Employee::PerformanceRating::PIP);
  employee.get_department_mutable() = "Finanace";
  EXPECT_EQ(employee.get_department(), "Finanace");
}

TEST(TestClass, Setter) {
  const auto personal_info = random_personal_info();
  std::string department{"IT"};
  const auto ratings = random_ratings();
  Employee employee(personal_info, department, ratings);

  const auto ratings2 = random_ratings();
  const auto personal_info2 = random_personal_info();
  std::string department2{"Finance"};
  employee.set_department(department2);
  employee.set_person_info(personal_info2);
  employee.set_year_to_perf(ratings2);

  EXPECT_EQ(employee.get_department(), department2);
  EXPECT_EQ(employee.get_person_info().name, personal_info2.name);
  EXPECT_EQ(employee.get_person_info().address.city,
            personal_info2.address.city);
  EXPECT_EQ(employee.get_year_to_perf().at(2015), ratings2.at(2015));

  EXPECT_NE(employee.get_department(), department);
  EXPECT_NE(employee.get_person_info().name, personal_info.name);
  EXPECT_NE(employee.get_person_info().address.city,
            personal_info.address.city);
}

TEST(TestClass, Assignment) {
  const auto personal_info = random_personal_info();
  std::string department{"IT"};
  const auto ratings = random_ratings();
  Employee employee1(personal_info, department, ratings);
  {
    Employee employee2 = employee1;
    EXPECT_EQ(employee1.get_department(), employee2.get_department());
    EXPECT_EQ(employee1.get_year_to_perf(), employee2.get_year_to_perf());
    EXPECT_EQ(employee1.get_person_info().name,
              employee2.get_person_info().name);
    EXPECT_EQ(employee1.get_person_info().phone,
              employee2.get_person_info().phone);
    EXPECT_EQ(employee1.get_person_info().address.state,
              employee2.get_person_info().address.state);
  }

  {
    Employee employee2;
    employee2 = employee1;
    EXPECT_EQ(employee1.get_department(), employee2.get_department());
    EXPECT_EQ(employee1.get_year_to_perf(), employee2.get_year_to_perf());
    EXPECT_EQ(employee1.get_person_info().name,
              employee2.get_person_info().name);
    EXPECT_EQ(employee1.get_person_info().phone,
              employee2.get_person_info().phone);
    EXPECT_EQ(employee1.get_person_info().address.state,
              employee2.get_person_info().address.state);
  }
}

TEST(TestClass, Serialization) {
  std::string department{"IT"};
  std::vector<Employee> vec;
  vec.emplace_back(
      Employee(random_personal_info(), department, random_ratings()));
  vec.emplace_back(
      Employee(random_personal_info(), department, random_ratings()));
  vec.emplace_back(
      Employee(random_personal_info(), department, random_ratings()));
  std::stringstream ss;
  {
    cereal::JSONOutputArchive archive(ss);
    archive(vec);
  }
  std::vector<Employee> vec2;
  ss.seekg(0);
  {
    cereal::JSONInputArchive archive(ss);
    archive(vec2);
  }
  EXPECT_EQ(vec2.size(), 3);
  EXPECT_EQ(vec[0].get_person_info().address.city,
            vec2[0].get_person_info().address.city);
}